
To smooth trajectories, we can use a Kalman filter. The implemented KalmanSmootherCV is based on the assumption of a nearly-constant velocity (CV) model. To use KalmanSmootherCV, the optional dependency StoneSoup needs to be installed.
import pandas as pd
import geopandas as gpd
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from matplotlib import pyplot as plt
import movingpandas as mpd
import warnings
warnings.filterwarnings('ignore')
mpd.show_versions()
MovingPandas 0.9.rc2 SYSTEM INFO ----------- python : 3.7.12 | packaged by conda-forge | (default, Oct 26 2021, 05:37:49) [MSC v.1916 64 bit (AMD64)] executable : E:\Anaconda\envs\mpd-ex\python.exe machine : Windows-10-10.0.19041-SP0 GEOS, GDAL, PROJ INFO --------------------- GEOS : None GEOS lib : None GDAL : 3.2.1 GDAL data dir: None PROJ : 7.2.0 PROJ data dir: E:\Anaconda\envs\mpd-ex\Library\share\proj PYTHON DEPENDENCIES ------------------- geopandas : 0.10.2 pandas : 1.3.5 fiona : 1.8.18 numpy : 1.21.5 shapely : 1.7.1 rtree : 0.9.7 pyproj : 3.1.0 matplotlib : 3.5.1 mapclassify: 2.4.3 geopy : 2.2.0 holoviews : 1.14.6 hvplot : 0.7.3 geoviews : 1.9.2
gdf = read_file('../data/geolife_small.gpkg')
traj_collection = mpd.TrajectoryCollection(gdf, 'trajectory_id', t='t')
split = mpd.ObservationGapSplitter(traj_collection).split(gap=timedelta(minutes=15))
plot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}
split.plot(column='trajectory_id', **plot_defaults)
<AxesSubplot:>
This smoother operates on the assumption of a nearly-constant velocity (CV) model. The process_noise_std and measurement_noise_std parameters can be used to tune the smoother:
process_noise_std governs the uncertainty associated with the adherence of the new (smooth) trajectories to the CV model assumption; higher values relax the assumption, therefore leading to less-smooth trajectories, and vice-versa.measurement_noise_std controls the assumed error in the original trajectories; higher values dictate that the original trajectories are expected to be noisier (and therefore, less reliable), thus leading to smoother trajectories, and vice-versa.Try tuning these parameters and observe the resulting trajectories:
help(mpd.KalmanSmootherCV)
Help on class KalmanSmootherCV in module movingpandas.trajectory_smoother: class KalmanSmootherCV(TrajectorySmoother) | KalmanSmootherCV(traj) | | Smooths using a Kalman Filter with a Constant Velocity model. | | The Constant Velocity model assumes that the speed between consecutive locations is | nearly constant. For trajectories where ``traj.is_latlon = True`` the smoother | converts to EPSG:3395 (World Mercator) internally to perform filtering and smoothing | | .. note:: | This class makes use of | `Stone Soup <https://stonesoup.readthedocs.io/en/latest/>`_, which is an | optional dependency and not installed by default. To use this class, you need | to install Stone Soup directly | (see `here <https://stonesoup.readthedocs.io/en/latest/#installation>`_). | | Method resolution order: | KalmanSmootherCV | TrajectorySmoother | abc.ABC | builtins.object | | Methods defined here: | | smooth(self, process_noise_std=0.5, measurement_noise_std=1) | Smooth the input Trajectory/TrajectoryCollection | | Parameters | ---------- | process_noise_std: float or sequence of floats of length 2, default is 1e-5. | The process (acceleration) noise standard deviation. | | If a sequence (e.g. list, tuple, etc.) is provided, the first index is used | for the x coordinate, while the second is used for the y coordinate. If | ``traj.is_latlon=True`` the values are applied to the easting and northing | coordinate (in EPSG:3395) respectively. | | Alternatively, a single float can be provided, which is assumed to be the | same for both coordinates. | measurement_noise_std: float or sequence of floats of length 2, default is 1. | The measurement noise standard deviation. | | If a sequence (e.g. list, tuple, etc.) is provided, the first index is used | for the x coordinate, while the second is used for the y coordinate. If | ``traj.is_latlon=True`` the values are applied to the easting and northing | coordinate (in EPSG:3395) respectively. | | Alternatively, a single float can be provided, which is assumed to be the | same for both coordinates. | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | __abstractmethods__ = frozenset() | | ---------------------------------------------------------------------- | Methods inherited from TrajectorySmoother: | | __init__(self, traj) | Create TrajectorySmoother | | Parameters | ---------- | traj : Trajectory or TrajectoryCollection | | ---------------------------------------------------------------------- | Data descriptors inherited from TrajectorySmoother: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
smooth = mpd.trajectory_smoother.KalmanSmootherCV(split).smooth(process_noise_std=0.1, measurement_noise_std=10)
print(smooth)
TrajectoryCollection with 11 trajectories
hvplot_defaults = {'tiles':'CartoLight', 'frame_height':320, 'frame_width':320, 'cmap':'Viridis', 'colorbar':True}
kwargs = {**hvplot_defaults, 'line_width':4}
(split.hvplot(title='Original Trajectories', **kwargs) +
smooth.hvplot(title='Smooth Trajectories', **kwargs))
kwargs = {**hvplot_defaults, 'c':'speed', 'line_width':7, 'clim':(0,20)}
(split.trajectories[2].hvplot(title='Original Trajectories', **kwargs) +
smooth.trajectories[2].hvplot(title='Smooth Trajectories', **kwargs))